home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 June / PersonalComputerWorld-June2009-CoverdiscCD.iso / Software / Freeware / Firebug 1.3.3 / firebug-1.3.3-fx.xpi / content / firebug / editor.js < prev    next >
Encoding:
JavaScript  |  2009-02-19  |  31.2 KB  |  1,113 lines

  1. /* See license.txt for terms of usage */
  2.  
  3. FBL.ns(function() { with (FBL) {
  4.  
  5. // ************************************************************************************************
  6. // Constants
  7.  
  8. const saveTimeout = 400;
  9. const pageAmount = 10;
  10.  
  11. // ************************************************************************************************
  12. // Globals
  13.  
  14. var currentTarget = null;
  15. var currentGroup = null;
  16. var currentPanel = null;
  17. var currentEditor = null;
  18.  
  19. var defaultEditor = null;
  20.  
  21. var originalClassName = null;
  22.  
  23. var originalValue = null;
  24. var defaultValue = null;
  25. var previousValue = null;
  26.  
  27. var invalidEditor = false;
  28. var ignoreNextInput = false;
  29.  
  30. // ************************************************************************************************
  31.  
  32. Firebug.Editor = extend(Firebug.Module,
  33. {
  34.     tabCharacter: "    ",
  35.  
  36.     startEditing: function(target, value, editor)
  37.     {
  38.         this.stopEditing();
  39.  
  40.         if (hasClass(target, "insertBefore") || hasClass(target, "insertAfter"))
  41.             return;
  42.  
  43.         var panel = Firebug.getElementPanel(target);
  44.         if (!panel.editable)
  45.             return;
  46.  
  47.         defaultValue = target.getAttribute("defaultValue");
  48.         if (value == undefined)
  49.         {
  50.             value = target.textContent;
  51.             if (value == defaultValue)
  52.                 value = "";
  53.         }
  54.  
  55.         originalValue = previousValue = value;
  56.  
  57.         invalidEditor = false;
  58.         currentTarget = target;
  59.         currentPanel = panel;
  60.         currentGroup = getAncestorByClass(target, "editGroup");
  61.  
  62.         currentPanel.editing = true;
  63.  
  64.         var panelEditor = currentPanel.getEditor(target, value);
  65.         currentEditor = editor ? editor : panelEditor;
  66.         if (!currentEditor)
  67.             currentEditor = getDefaultEditor(currentPanel);
  68.  
  69.         var inlineParent = getInlineParent(target);
  70.         var targetSize = getOffsetSize(inlineParent);
  71.  
  72.         setClass(panel.panelNode, "editing");
  73.         setClass(target, "editing");
  74.         if (currentGroup)
  75.             setClass(currentGroup, "editing");
  76.  
  77.         currentEditor.show(target, currentPanel, value, targetSize);
  78.         currentEditor.beginEditing(target, value);
  79.  
  80.         this.attachListeners(currentEditor, panel.context);
  81.     },
  82.  
  83.     stopEditing: function(cancel)
  84.     {
  85.         if (!currentTarget)
  86.             return;
  87.  
  88.         clearTimeout(this.saveTimeout);
  89.         delete this.saveTimeout;
  90.  
  91.         this.detachListeners(currentEditor, currentPanel.context);
  92.  
  93.         removeClass(currentPanel.panelNode, "editing");
  94.         removeClass(currentTarget, "editing");
  95.         if (currentGroup)
  96.             removeClass(currentGroup, "editing");
  97.  
  98.         var value = currentEditor.getValue();
  99.         if (value == defaultValue)
  100.             value = "";
  101.  
  102.         var removeGroup = currentEditor.endEditing(currentTarget, value, cancel);
  103.  
  104.         try
  105.         {
  106.             if (cancel)
  107.             {
  108.                 if (value != originalValue)
  109.                     currentEditor.saveEdit(currentTarget, originalValue, previousValue);
  110.  
  111.                 if (removeGroup && !originalValue && currentGroup)
  112.                     currentGroup.parentNode.removeChild(currentGroup);
  113.             }
  114.             else if (!value)
  115.             {
  116.                 currentEditor.saveEdit(currentTarget, null, previousValue);
  117.  
  118.                 if (removeGroup && currentGroup)
  119.                     currentGroup.parentNode.removeChild(currentGroup);
  120.             }
  121.             else
  122.                 this.save(value);
  123.         }
  124.         catch (exc)
  125.         {
  126.             ERROR(exc);
  127.         }
  128.  
  129.         currentEditor.hide();
  130.         currentPanel.editing = false;
  131.  
  132.         currentTarget = null;
  133.         currentGroup = null;
  134.         currentPanel = null;
  135.         currentEditor = null;
  136.         originalValue = null;
  137.         invalidEditor = false;
  138.  
  139.         return value;
  140.     },
  141.  
  142.     cancelEditing: function()
  143.     {
  144.         return this.stopEditing(true);
  145.     },
  146.  
  147.     update: function(saveNow)
  148.     {
  149.         if (this.saveTimeout)
  150.             clearTimeout(this.saveTimeout);
  151.  
  152.         invalidEditor = true;
  153.  
  154.         currentEditor.layout();
  155.  
  156.         if (saveNow)
  157.             this.save();
  158.         else
  159.         {
  160.             var context = currentPanel.context;
  161.             this.saveTimeout = context.setTimeout(bindFixed(this.save, this), saveTimeout);
  162.         }
  163.     },
  164.  
  165.     save: function(value)
  166.     {
  167.         if (!invalidEditor)
  168.             return;
  169.  
  170.         if (value == undefined)
  171.             value = currentEditor.getValue();
  172.  
  173.         try
  174.         {
  175.             currentEditor.saveEdit(currentTarget, value, previousValue);
  176.  
  177.             previousValue = value;
  178.             invalidEditor = false;
  179.         }
  180.         catch (exc)
  181.         {
  182.             ERROR(exc);
  183.         }
  184.     },
  185.  
  186.     setEditTarget: function(element)
  187.     {
  188.         if (!element)
  189.             this.stopEditing();
  190.         else if (hasClass(element, "insertBefore"))
  191.             this.insertRow(element, "before");
  192.         else if (hasClass(element, "insertAfter"))
  193.             this.insertRow(element, "after");
  194.         else
  195.             this.startEditing(element, undefined, currentEditor);
  196.     },
  197.  
  198.     tabNextEditor: function()
  199.     {
  200.         if (!currentTarget)
  201.             return;
  202.  
  203.         var value = currentEditor.getValue();
  204.         var nextEditable = currentTarget;
  205.         do
  206.         {
  207.             nextEditable = !value && currentGroup
  208.                 ? getNextOutsider(nextEditable, currentGroup)
  209.                 : getNextByClass(nextEditable, "editable");
  210.         }
  211.         while (nextEditable && !nextEditable.offsetHeight);
  212.  
  213.         this.setEditTarget(nextEditable);
  214.     },
  215.  
  216.     tabPreviousEditor: function()
  217.     {
  218.         if (!currentTarget)
  219.             return;
  220.  
  221.         var value = currentEditor.getValue();
  222.         var prevEditable = currentTarget;
  223.         do
  224.         {
  225.             prevEditable = !value && currentGroup
  226.                 ? getPreviousOutsider(prevEditable, currentGroup)
  227.                 : getPreviousByClass(prevEditable, "editable");
  228.         }
  229.         while (prevEditable && !prevEditable.offsetHeight);
  230.  
  231.         this.setEditTarget(prevEditable);
  232.     },
  233.  
  234.     insertRow: function(relative, insertWhere)
  235.     {
  236.         var value = this.stopEditing();
  237.  
  238.         if (!relative)
  239.             relative = currentTarget;
  240.  
  241.         var group = getAncestorByClass(relative, "editGroup");
  242.         if (!group)
  243.             group = relative;
  244.  
  245.         currentPanel = Firebug.getElementPanel(group);
  246.  
  247.         currentEditor = currentPanel.getEditor(group, value);
  248.         if (!currentEditor)
  249.             currentEditor = getDefaultEditor(currentPanel);
  250.  
  251.         currentGroup = currentEditor.insertNewRow(group, insertWhere);
  252.         if (!currentGroup)
  253.             return;
  254.  
  255.         var editable = hasClass(currentGroup, "editable")
  256.             ? currentGroup
  257.             : getNextByClass(currentGroup, "editable");
  258.  
  259.         if (editable)
  260.             this.setEditTarget(editable);
  261.     },
  262.  
  263.     insertRowForObject: function(relative)
  264.     {
  265.         var container = getAncestorByClass(relative, "insertInto");
  266.         if (container)
  267.         {
  268.             relative = getChildByClass(container, "insertBefore");
  269.             if (relative)
  270.                 this.insertRow(relative, "before");
  271.         }
  272.     },
  273.  
  274.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  275.  
  276.     attachListeners: function(editor, context)
  277.     {
  278.         var win = currentTarget.ownerDocument.defaultView;
  279.         win.addEventListener("resize", this.onResize, true);
  280.         win.addEventListener("blur", this.onBlur, true);
  281.  
  282.         var chrome = context.chrome;
  283.  
  284.         this.listeners = [
  285.             chrome.keyCodeListen("ESCAPE", null, bind(this.cancelEditing, this)),
  286.         ];
  287.  
  288.         if (editor.arrowCompletion)
  289.         {
  290.             this.listeners.push(
  291.                 chrome.keyCodeListen("UP", null, bindFixed(editor.completeValue, editor, -1)),
  292.                 chrome.keyCodeListen("DOWN", null, bindFixed(editor.completeValue, editor, 1)),
  293.                 chrome.keyCodeListen("PAGE_UP", null, bindFixed(editor.completeValue, editor, -pageAmount)),
  294.                 chrome.keyCodeListen("PAGE_DOWN", null, bindFixed(editor.completeValue, editor, pageAmount))
  295.             );
  296.         }
  297.  
  298.         if (currentEditor.tabNavigation)
  299.         {
  300.             this.listeners.push(
  301.                 chrome.keyCodeListen("RETURN", null, bind(this.tabNextEditor, this)),
  302.                 chrome.keyCodeListen("RETURN", isControl, bind(this.insertRow, this, null, "after")),
  303.                 chrome.keyCodeListen("TAB", null, bind(this.tabNextEditor, this)),
  304.                 chrome.keyCodeListen("TAB", isShift, bind(this.tabPreviousEditor, this))
  305.             );
  306.         }
  307.         else if (currentEditor.multiLine)
  308.         {
  309.             this.listeners.push(
  310.                 chrome.keyCodeListen("TAB", null, insertTab)
  311.             );
  312.         }
  313.         else
  314.         {
  315.             this.listeners.push(
  316.                 chrome.keyCodeListen("RETURN", null, bindFixed(this.stopEditing, this))
  317.             );
  318.  
  319.             if (currentEditor.tabCompletion)
  320.             {
  321.                 this.listeners.push(
  322.                     chrome.keyCodeListen("TAB", null, bind(editor.completeValue, editor, 1)),
  323.                     chrome.keyCodeListen("TAB", isShift, bind(editor.completeValue, editor, -1))
  324.                 );
  325.             }
  326.         }
  327.     },
  328.  
  329.     detachListeners: function(editor, context)
  330.     {
  331.         if (!this.listeners)
  332.             return;
  333.  
  334.         var win = currentTarget.ownerDocument.defaultView;
  335.         win.removeEventListener("resize", this.onResize, true);
  336.         win.removeEventListener("blur", this.onBlur, true);
  337.  
  338.         var chrome = context.chrome;
  339.         if (chrome)
  340.         {
  341.             for (var i = 0; i < this.listeners.length; ++i)
  342.                 chrome.keyIgnore(this.listeners[i]);
  343.         }
  344.  
  345.         delete this.listeners;
  346.     },
  347.  
  348.     onResize: function(event)
  349.     {
  350.         currentEditor.layout(true);
  351.     },
  352.  
  353.     onBlur: function(event)
  354.     {
  355.         if (currentEditor.enterOnBlur && isAncestor(event.target, currentEditor.box))
  356.             this.stopEditing();
  357.     },
  358.  
  359.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  360.     // extends Module
  361.  
  362.     initialize: function()
  363.     {
  364.         this.onResize = bindFixed(this.onResize, this);
  365.         this.onBlur = bind(this.onBlur, this);
  366.     },
  367.  
  368.     disable: function()
  369.     {
  370.         this.stopEditing();
  371.     },
  372.  
  373.     showContext: function(browser, context)
  374.     {
  375.         this.stopEditing();
  376.     },
  377.  
  378.     showPanel: function(browser, panel)
  379.     {
  380.         this.stopEditing();
  381.     }
  382. });
  383.  
  384. // ************************************************************************************************
  385. // BaseEditor
  386.  
  387. Firebug.BaseEditor =
  388. {
  389.     getValue: function()
  390.     {
  391.     },
  392.  
  393.     setValue: function(value)
  394.     {
  395.     },
  396.  
  397.     show: function(target, panel, value, textSize, targetSize)
  398.     {
  399.     },
  400.  
  401.     hide: function()
  402.     {
  403.     },
  404.  
  405.     layout: function(forceAll)
  406.     {
  407.     },
  408.  
  409.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  410.  
  411.     beginEditing: function(target, value)
  412.     {
  413.     },
  414.  
  415.     saveEdit: function(target, value, previousValue)
  416.     {
  417.     },
  418.  
  419.     endEditing: function(target, value, cancel)
  420.     {
  421.         // Remove empty groups by default
  422.         return true;
  423.     },
  424.  
  425.     insertNewRow: function(target, insertWhere)
  426.     {
  427.     },
  428.  
  429.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  430.  
  431.     startMeasuring: function(target)
  432.     {
  433.         if (!this.measureBox)
  434.         {
  435.             this.measureBox = target.ownerDocument.createElement("span");
  436.             this.measureBox.className = "measureBox";
  437.         }
  438.  
  439.         copyTextStyles(target, this.measureBox);
  440.         target.ownerDocument.body.appendChild(this.measureBox);
  441.     },
  442.  
  443.     measureText: function(value)
  444.     {
  445.         this.measureBox.innerHTML = value ? escapeHTML(value) : "m";
  446.         return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1};
  447.     },
  448.  
  449.     stopMeasuring: function()
  450.     {
  451.         this.measureBox.parentNode.removeChild(this.measureBox);
  452.     }
  453. };
  454.  
  455. // ************************************************************************************************
  456. // InlineEditor
  457.  
  458. Firebug.InlineEditor = function(doc)
  459. {
  460.     this.initializeInline(doc);
  461. };
  462.  
  463. Firebug.InlineEditor.prototype = domplate(Firebug.BaseEditor,
  464. {
  465.     tag:
  466.         DIV({class: "inlineEditor"},
  467.             DIV({class: "textEditorTop1"},
  468.                 DIV({class: "textEditorTop2"})
  469.             ),
  470.             DIV({class: "textEditorInner1"},
  471.                 DIV({class: "textEditorInner2"},
  472.                     INPUT({class: "textEditorInner", type: "text",
  473.                         oninput: "$onInput", onkeypress: "$onKeyPress", onoverflow: "$onOverflow"})
  474.                 )
  475.             ),
  476.             DIV({class: "textEditorBottom1"},
  477.                 DIV({class: "textEditorBottom2"})
  478.             )
  479.         ),
  480.  
  481.     expanderTag: IMG({class: "inlineExpander", src: "blank.gif"}),
  482.  
  483.     enterOnBlur: true,
  484.     outerMargin: 8,
  485.     shadowExpand: 7,
  486.  
  487.     initialize: function()
  488.     {
  489.         this.fixedWidth = false;
  490.         this.completeAsYouType = true;
  491.         this.tabNavigation = true;
  492.         this.multiLine = false;
  493.         this.tabCompletion = false;
  494.         this.arrowCompletion = true;
  495.         this.noWrap = true;
  496.         this.numeric = false;
  497.     },
  498.  
  499.     destroy: function()
  500.     {
  501.         this.destroyInput();
  502.     },
  503.  
  504.     initializeInline: function(doc)
  505.     {
  506.         this.box = this.tag.replace({}, doc, this);
  507.         this.input = this.box.childNodes[1].firstChild.firstChild;  // XXXjjb childNode[1] required
  508.         this.expander = this.expanderTag.replace({}, doc, this);
  509.         this.initialize();
  510.     },
  511.  
  512.     destroyInput: function()
  513.     {
  514.         // XXXjoe Need to remove input/keypress handlers to avoid leaks
  515.     },
  516.  
  517.     getValue: function()
  518.     {
  519.         return this.input.value;
  520.     },
  521.  
  522.     setValue: function(value)
  523.     {
  524.         // It's only a one-line editor, so new lines shouldn't be allowed
  525.         return this.input.value = stripNewLines(value);
  526.     },
  527.  
  528.     show: function(target, panel, value, targetSize)
  529.     {
  530.         this.target = target;
  531.         this.panel = panel;
  532.  
  533.         this.targetSize = targetSize;
  534.         this.targetOffset = getClientOffset(target);
  535.  
  536.         this.originalClassName = this.box.className;
  537.  
  538.         var classNames = target.className.split(" ");
  539.         for (var i = 0; i < classNames.length; ++i)
  540.             setClass(this.box, "editor-" + classNames[i]);
  541.  
  542.         // Make the editor match the target's font style
  543.         copyTextStyles(target, this.box);
  544.  
  545.         this.setValue(value);
  546.  
  547.         if (this.fixedWidth)
  548.             this.updateLayout(true);
  549.         else
  550.         {
  551.             this.startMeasuring(target);
  552.             this.textSize = this.measureText(value);
  553.  
  554.             // Correct the height of the box to make the funky CSS drop-shadow line up
  555.             var parent = this.input.parentNode;
  556.             if (hasClass(parent, "textEditorInner2"))
  557.             {
  558.                 var yDiff = this.textSize.height - this.shadowExpand;
  559.                 parent.style.height = yDiff + "px";
  560.                 parent.parentNode.style.height = yDiff + "px";
  561.             }
  562.  
  563.             this.updateLayout(true);
  564.         }
  565.  
  566.         this.getAutoCompleter().reset();
  567.  
  568.         panel.panelNode.appendChild(this.box);
  569.         this.input.select();
  570.  
  571.         // Insert the "expander" to cover the target element with white space
  572.         if (!this.fixedWidth)
  573.         {
  574.             copyBoxStyles(target, this.expander);
  575.  
  576.             target.parentNode.replaceChild(this.expander, target);
  577.             collapse(target, true);
  578.             this.expander.parentNode.insertBefore(target, this.expander);
  579.         }
  580.  
  581.         scrollIntoCenterView(this.box, null, true);
  582.     },
  583.  
  584.     hide: function()
  585.     {
  586.         this.box.className = this.originalClassName;
  587.  
  588.         if (!this.fixedWidth)
  589.         {
  590.             this.stopMeasuring();
  591.  
  592.             collapse(this.target, false);
  593.  
  594.             if (this.expander.parentNode)
  595.                 this.expander.parentNode.removeChild(this.expander);
  596.         }
  597.  
  598.         if (this.box.parentNode)
  599.         {
  600.             try { this.input.setSelectionRange(0, 0); } catch (exc) {}
  601.             this.box.parentNode.removeChild(this.box);
  602.         }
  603.  
  604.         delete this.target;
  605.         delete this.panel;
  606.     },
  607.  
  608.     layout: function(forceAll)
  609.     {
  610.         if (!this.fixedWidth)
  611.             this.textSize = this.measureText(this.input.value);
  612.  
  613.         if (forceAll)
  614.             this.targetOffset = getClientOffset(this.expander);
  615.  
  616.         this.updateLayout(false, forceAll);
  617.     },
  618.  
  619.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  620.  
  621.     beginEditing: function(target, value)
  622.     {
  623.     },
  624.  
  625.     saveEdit: function(target, value, previousValue)
  626.     {
  627.     },
  628.  
  629.     endEditing: function(target, value, cancel)
  630.     {
  631.         // Remove empty groups by default
  632.         return true;
  633.     },
  634.  
  635.     insertNewRow: function(target, insertWhere)
  636.     {
  637.     },
  638.  
  639.     advanceToNext: function(target, charCode)
  640.     {
  641.         return false;
  642.     },
  643.  
  644.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  645.  
  646.     getAutoCompleteRange: function(value, offset)
  647.     {
  648.     },
  649.  
  650.     getAutoCompleteList: function(preExpr, expr, postExpr)
  651.     {
  652.     },
  653.  
  654.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  655.  
  656.     getAutoCompleter: function()
  657.     {
  658.         if (!this.autoCompleter)
  659.         {
  660.             this.autoCompleter = new Firebug.AutoCompleter(null,
  661.                 bind(this.getAutoCompleteRange, this), bind(this.getAutoCompleteList, this),
  662.                 true, false);
  663.         }
  664.  
  665.         return this.autoCompleter;
  666.     },
  667.  
  668.     completeValue: function(amt)
  669.     {
  670.         if (this.getAutoCompleter().complete(currentPanel.context, this.input, true, amt < 0))
  671.             Firebug.Editor.update(true);
  672.         else
  673.             this.incrementValue(amt);
  674.     },
  675.  
  676.     incrementValue: function(amt)
  677.     {
  678.         var value = this.input.value;
  679.         var start = this.input.selectionStart, end = this.input.selectionEnd;
  680.  
  681.         var range = this.getAutoCompleteRange(value, start);
  682.         if (!range || range.type != "int")
  683.             range = {start: 0, end: value.length-1};
  684.  
  685.         var expr = value.substr(range.start, range.end-range.start+1);
  686.         preExpr = value.substr(0, range.start);
  687.         postExpr = value.substr(range.end+1);
  688.  
  689.         // See if the value is an integer, and if so increment it
  690.         var intValue = parseInt(expr);
  691.         if (!!intValue || intValue == 0)
  692.         {
  693.             var m = /\d+/.exec(expr);
  694.             var digitPost = expr.substr(m.index+m[0].length);
  695.  
  696.             var completion = intValue-amt;
  697.             this.input.value = preExpr + completion + digitPost + postExpr;
  698.             this.input.setSelectionRange(start, end);
  699.  
  700.             Firebug.Editor.update(true);
  701.  
  702.             return true;
  703.         }
  704.         else
  705.             return false;
  706.     },
  707.  
  708.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  709.  
  710.     onKeyPress: function(event)
  711.     {
  712.         if (event.keyCode == 27 && !this.completeAsYouType)
  713.         {
  714.             var reverted = this.getAutoCompleter().revert(this.input);
  715.             if (reverted)
  716.                 cancelEvent(event);
  717.         }
  718.         else if (event.charCode && this.advanceToNext(this.target, event.charCode))
  719.         {
  720.             Firebug.Editor.tabNextEditor();
  721.             cancelEvent(event);
  722.         }
  723.         else
  724.         {
  725.             if (this.numeric && event.charCode && (event.charCode < 48 || event.charCode > 57)
  726.                 && event.charCode != 45 && event.charCode != 46)
  727.                 FBL.cancelEvent(event);
  728.             else
  729.             {
  730.                 // If the user backspaces, don't autocomplete after the upcoming input event
  731.                 this.ignoreNextInput = event.keyCode == 8;
  732.             }
  733.         }
  734.     },
  735.  
  736.     onOverflow: function()
  737.     {
  738.         this.updateLayout(false, false, 3);
  739.     },
  740.  
  741.     onInput: function()
  742.     {
  743.         if (this.ignoreNextInput)
  744.         {
  745.             this.ignoreNextInput = false;
  746.             this.getAutoCompleter().reset();
  747.         }
  748.         else if (this.completeAsYouType)
  749.             this.getAutoCompleter().complete(currentPanel.context, this.input, false);
  750.         else
  751.             this.getAutoCompleter().reset();
  752.  
  753.         Firebug.Editor.update();
  754.     },
  755.  
  756.     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  757.  
  758.     updateLayout: function(initial, forceAll, extraWidth)
  759.     {
  760.         if (this.fixedWidth)
  761.         {
  762.             this.box.style.left = (this.targetOffset.x) + "px";
  763.             this.box.style.top = (this.targetOffset.y) + "px";
  764.  
  765.             var w = this.target.offsetWidth;
  766.             var h = this.target.offsetHeight;
  767.             this.input.style.width = w + "px";
  768.             this.input.style.height = (h-3) + "px";
  769.         }
  770.         else
  771.         {
  772.             if (initial || forceAll)
  773.             {
  774.                 this.box.style.left = this.targetOffset.x + "px";
  775.                 this.box.style.top = this.targetOffset.y + "px";
  776.             }
  777.  
  778.             var approxTextWidth = this.textSize.width;
  779.             var maxWidth = (currentPanel.panelNode.scrollWidth - this.targetOffset.x)
  780.                 - this.outerMargin;
  781.  
  782.             var wrapped = initial
  783.                 ? this.noWrap && this.targetSize.height > this.textSize.height+3
  784.                 : this.noWrap && approxTextWidth > maxWidth;
  785.  
  786.             if (wrapped)
  787.             {
  788.                 var style = this.target.ownerDocument.defaultView.getComputedStyle(this.target, "");
  789.                 targetMargin = parseInt(style.marginLeft) + parseInt(style.marginRight);
  790.  
  791.                 // Make the width fit the remaining x-space from the offset to the far right
  792.                 approxTextWidth = maxWidth - targetMargin;
  793.  
  794.                 this.input.style.width = "100%";
  795.                 this.box.style.width = approxTextWidth + "px";
  796.             }
  797.             else
  798.             {
  799.                 // Make the input one character wider than the text value so that
  800.                 // typing does not ever cause the textbox to scroll
  801.                 var charWidth = this.textSize.width / Math.max(this.input.value.length, 1);
  802.  
  803.                 // Sometimes we need to make the editor a little wider, specifically when
  804.                 // an overflow happens, otherwise it will scroll off some text on the left
  805.                 if (extraWidth)
  806.                     charWidth *= extraWidth;
  807.  
  808.                 var inputWidth = approxTextWidth + charWidth;
  809.  
  810.                 if (initial)
  811.                     this.box.style.width = "auto";
  812.                 else
  813.                 {
  814.                     var xDiff = this.box.scrollWidth - this.input.offsetWidth;
  815.                     this.box.style.width = (inputWidth + xDiff) + "px";
  816.                 }
  817.  
  818.                 this.input.style.width = inputWidth + "px";
  819.             }
  820.  
  821.             this.expander.style.width = approxTextWidth + "px";
  822.             this.expander.style.height = (this.textSize.height-3) + "px";
  823.         }
  824.  
  825.         if (forceAll)
  826.             scrollIntoCenterView(this.box, null, true);
  827.     }
  828. });
  829.  
  830. // ************************************************************************************************
  831. // Autocompletion
  832.  
  833. Firebug.AutoCompleter = function(getExprOffset, getRange, evaluator, selectMode, caseSensitive)
  834. {
  835.     var candidates = null;
  836.     var originalValue = null;
  837.     var originalOffset = -1;
  838.     var lastExpr = null;
  839.     var lastOffset = -1;
  840.     var exprOffset = 0;
  841.     var lastIndex = 0;
  842.     var preParsed = null;
  843.     var preExpr = null;
  844.     var postExpr = null;
  845.  
  846.     this.revert = function(textBox)
  847.     {
  848.         if (originalOffset != -1)
  849.         {
  850.             textBox.value = originalValue;
  851.             textBox.setSelectionRange(originalOffset, originalOffset);
  852.  
  853.             this.reset();
  854.             return true;
  855.         }
  856.         else
  857.         {
  858.             this.reset();
  859.             return false;
  860.         }
  861.     };
  862.  
  863.     this.reset = function()
  864.     {
  865.         candidates = null;
  866.         originalValue = null;
  867.         originalOffset = -1;
  868.         lastExpr = null;
  869.         lastOffset = 0;
  870.         exprOffset = 0;
  871.     };
  872.  
  873.     this.complete = function(context, textBox, cycle, reverse)
  874.     {
  875.         var value = lastValue = textBox.value;
  876.         var offset = textBox.selectionStart;
  877.         if (!selectMode && originalOffset != -1)
  878.             offset = originalOffset;
  879.  
  880.         if (!candidates || !cycle || offset != lastOffset)
  881.         {
  882.             originalOffset = offset;
  883.             originalValue = value;
  884.  
  885.             // Find the part of the string that will be parsed
  886.             var parseStart = getExprOffset ? getExprOffset(value, offset, context) : 0;
  887.             preParsed = value.substr(0, parseStart);
  888.             var parsed = value.substr(parseStart);
  889.  
  890.             // Find the part of the string that is being completed
  891.             var range = getRange ? getRange(parsed, offset-parseStart, context) : null;
  892.             if (!range)
  893.                 range = {start: 0, end: parsed.length-1 };
  894.  
  895.             var expr = parsed.substr(range.start, range.end-range.start+1);
  896.             preExpr = parsed.substr(0, range.start);
  897.             postExpr = parsed.substr(range.end+1);
  898.             exprOffset = parseStart + range.start;
  899.  
  900.             if (!cycle)
  901.             {
  902.                 if (!expr)
  903.                     return;
  904.                 else if (lastExpr && lastExpr.indexOf(expr) != 0)
  905.                 {
  906.                     candidates = null;
  907.                 }
  908.                 else if (lastExpr && lastExpr.length >= expr.length)
  909.                 {
  910.                     candidates = null;
  911.                     lastExpr = expr;
  912.                     return;
  913.                 }
  914.             }
  915.  
  916.             lastExpr = expr;
  917.             lastOffset = offset;
  918.  
  919.             var searchExpr;
  920.  
  921.             // Check if the cursor is at the very right edge of the expression, or
  922.             // somewhere in the middle of it
  923.             if (expr && offset != parseStart+range.end+1)
  924.             {
  925.                 if (cycle)
  926.                 {
  927.                     // We are in the middle of the expression, but we can
  928.                     // complete by cycling to the next item in the values
  929.                     // list after the expression
  930.                     offset = range.start;
  931.                     searchExpr = expr;
  932.                     expr = "";
  933.                 }
  934.                 else
  935.                 {
  936.                     // We can't complete unless we are at the ridge edge
  937.                     return;
  938.                 }
  939.             }
  940.  
  941.             var values = evaluator(preExpr, expr, postExpr, context);
  942.             if (!values)
  943.                 return;
  944.  
  945.             if (expr)
  946.             {
  947.                 // Filter the list of values to those which begin with expr. We
  948.                 // will then go on to complete the first value in the resulting list
  949.                 candidates = [];
  950.  
  951.                 if (caseSensitive)
  952.                 {
  953.                     for (var i = 0; i < values.length; ++i)
  954.                     {
  955.                         var name = values[i];
  956.                         if (name.indexOf && name.indexOf(expr) == 0)
  957.                             candidates.push(name);
  958.                     }
  959.                 }
  960.                 else
  961.                 {
  962.                     var lowerExpr = caseSensitive ? expr : expr.toLowerCase();
  963.                     for (var i = 0; i < values.length; ++i)
  964.                     {
  965.                         var name = values[i];
  966.                         if (name.indexOf && name.toLowerCase().indexOf(lowerExpr) == 0)
  967.                             candidates.push(name);
  968.                     }
  969.                 }
  970.  
  971.                 lastIndex = reverse ? candidates.length-1 : 0;
  972.             }
  973.             else if (searchExpr)
  974.             {
  975.                 var searchIndex = -1;
  976.  
  977.                 // Find the first instance of searchExpr in the values list. We
  978.                 // will then complete the string that is found
  979.                 if (caseSensitive)
  980.                 {
  981.                     searchIndex = values.indexOf(expr);
  982.                 }
  983.                 else
  984.                 {
  985.                     var lowerExpr = searchExpr.toLowerCase();
  986.                     for (var i = 0; i < values.length; ++i)
  987.                     {
  988.                         var name = values[i];
  989.                         if (name && name.toLowerCase().indexOf(lowerExpr) == 0)
  990.                         {
  991.                             searchIndex = i;
  992.                             break;
  993.                         }
  994.                     }
  995.                 }
  996.  
  997.                 // Nothing found, so there's nothing to complete to
  998.                 if (searchIndex == -1)
  999.                     return this.reset();
  1000.  
  1001.                 expr = searchExpr;
  1002.                 candidates = cloneArray(values);
  1003.                 lastIndex = searchIndex;
  1004.             }
  1005.             else
  1006.             {
  1007.                 expr = "";
  1008.                 candidates = [];
  1009.                 for (var i = 0; i < values.length; ++i)
  1010.                 {
  1011.                     if (values[i].substr)
  1012.                         candidates.push(values[i]);
  1013.                 }
  1014.                 lastIndex = -1;
  1015.             }
  1016.         }
  1017.  
  1018.         if (cycle)
  1019.         {
  1020.             expr = lastExpr;
  1021.             lastIndex += reverse ? -1 : 1;
  1022.         }
  1023.  
  1024.         if (!candidates.length)
  1025.             return;
  1026.  
  1027.         if (lastIndex >= candidates.length)
  1028.             lastIndex = 0;
  1029.         else if (lastIndex < 0)
  1030.             lastIndex = candidates.length-1;
  1031.  
  1032.         var completion = candidates[lastIndex];
  1033.         var preCompletion = expr.substr(0, offset-exprOffset);
  1034.         var postCompletion = completion.substr(offset-exprOffset);
  1035.  
  1036.         textBox.value = preParsed + preExpr + preCompletion + postCompletion + postExpr;
  1037.         var offsetEnd = preParsed.length + preExpr.length + completion.length;
  1038.         if (selectMode)
  1039.             textBox.setSelectionRange(offset, offsetEnd);
  1040.         else
  1041.             textBox.setSelectionRange(offsetEnd, offsetEnd);
  1042.  
  1043.         return true;
  1044.     };
  1045. };
  1046.  
  1047. // ************************************************************************************************
  1048. // Local Helpers
  1049.  
  1050. function getDefaultEditor(panel)
  1051. {
  1052.     if (!defaultEditor)
  1053.     {
  1054.         var doc = panel.document;
  1055.         defaultEditor = new Firebug.InlineEditor(doc);
  1056.     }
  1057.  
  1058.     return defaultEditor;
  1059. }
  1060.  
  1061. function getOutsider(element, group, stepper)
  1062. {
  1063.     var next = stepper(element);
  1064.     if (isAncestor(next, group))
  1065.     {
  1066.         do
  1067.         {
  1068.             next = stepper(next);
  1069.         }
  1070.         while (isAncestor(next, group) || hasClass(next, "insertBefore")
  1071.             || hasClass(next, "insertAfter"));
  1072.     }
  1073.  
  1074.     return next;
  1075. }
  1076.  
  1077. function getNextOutsider(element, group)
  1078. {
  1079.     return getOutsider(element, group, bind(getNextByClass, FBL, "editable"));
  1080. }
  1081.  
  1082. function getPreviousOutsider(element, group)
  1083. {
  1084.     return getOutsider(element, group, bind(getPreviousByClass, FBL, "editable"));
  1085. }
  1086.  
  1087. function getInlineParent(element)
  1088. {
  1089.     var lastInline = element;
  1090.     for (; element; element = element.parentNode)
  1091.     {
  1092.         var s = element.ownerDocument.defaultView.getComputedStyle(element, "");
  1093.         if (s.display != "inline")
  1094.             return lastInline;
  1095.         else
  1096.             lastInline = element;
  1097.     }
  1098.     return null;
  1099. }
  1100.  
  1101. function insertTab()
  1102. {
  1103.     insertTextIntoElement(currentEditor.input, Firebug.Editor.tabCharacter);
  1104. }
  1105.  
  1106. // ************************************************************************************************
  1107.  
  1108. Firebug.registerModule(Firebug.Editor);
  1109.  
  1110. // ************************************************************************************************
  1111.  
  1112. }});
  1113.